#include <stdio.h>
#include <iostream>
#include <list>
#include <vector>
#include <thread>

#include <web_assets_webssh.h>

#include "defer.h"

#include <cmdline.h>

#include "libssh2_cpp.h"

#include "wspp_fileserver.h"

#define ASIO_STANDALONE
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

typedef websocketpp::server<websocketpp::config::asio> asio_server;
typedef websocketpp::http::parser::request   ws_request;
typedef websocketpp::http::parser::response  ws_response;
typedef websocketpp::server<websocketpp::config::asio>::message_ptr     ws_msg;
typedef websocketpp::connection_hdl                                     ws_connection_hdl;
typedef websocketpp::server<websocketpp::config::asio>::connection_ptr  ws_connection_ptr;
typedef websocketpp::http::parameter_list http_param_list;

static std::string strPrefix = "";

const char* strIndexHtml_1 = 
            "<!DOCTYPE html>\r\n"
            "<html lang=\"en\">\r\n"
            "<head>\r\n"
            "   <meta charset=\"utf-8\">\r\n"
            "   <title>Simple TTY</title>\r\n"
            "</head>\r\n"
            "<style>\r\n"
            "  body{margin:0; padding:0;}\r\n"
            "</style>\r\n"
            "<body>\r\n"
            "<iframe id=\"mainiframe\" align=\"center\" width=\"100%\" frameborder=\"0\" scrolling=\"auto\" onload=\"changeFrameHeight()\" src=\"./static/terminal.html?";

const char* strIndexHtml_2 = 
            "hostname=127.0.0.1\"></iframe>\r\n"
            "<script language=\"javascript\" type=\"text/javascript\">\r\n"
            "function changeFrameHeight(){\r\n"
            "	var iframe= document.getElementById(\"mainiframe\");\r\n"
            "	try {\r\n"
            "        //bHeight 和 dHeight 如果相等，用其一等于iframe.height 即可\r\n"
            "        var bHeight = iframe.contentWindow.document.body.scrollHeight;\r\n"
            "        //var dHeight = iframe.contentWindow.document.documentElement.scrollHeight;\r\n"
            "        // var height = Math.max(bHeight, dHeight);\r\n"
            "        //console.log(dHeight)\r\n"
            "        //iframe.height = dHeight;\r\n"
            "		iframe.height = bHeight;\r\n"
            "    } catch (ex) { }\r\n"
            "}\r\n"
            "window.onresize = function(){ changeFrameHeight();}\r\n"
            "//window.setInterval(\"changeFrameHeight()\", 200);\r\n"
            "</script>\r\n"
            "</body>\r\n"
            "</html>\r\n";

// Define a callback to handle incoming messages
static bool _http_get_index_html(ws_connection_hdl& con, const Request<ws_request>& req, Response<ws_response>& rsp) {
    rsp.set_status(200);
    rsp.set_body(std::string(strIndexHtml_1) + strPrefix + std::string(strIndexHtml_2));
    rsp.set_header(std::string("Content-Type"), std::string("text/html; charset=utf-8"));
    return true;
}

typedef struct ssh_client_t {
    std::mutex list_mutex;
    std::map<ws_connection_ptr, libssh2_cpp::Ssh2Shell*> client_list;
}ssh_client_t;

static std::string _get_param_in_list(const RequestQueryParams& params, const std::string& key, const std::string& def_val)
{
    auto it = params.find(key);
    if (it != params.end()) {
        return it->second;
    }
    return def_val;
}

// 子进程退出了
static void _ssh_exit(WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>* srv_, ssh_client_t* ssh_, ws_connection_hdl hdl)
{
    auto conn_ = ((asio_server*)(srv_->get_endpoint()))->get_con_from_hdl(hdl);

    // 远程shell执行exit命令时，会在这儿崩溃
#if 0
    ssh_->list_mutex.lock();
    auto it = ssh_->client_list.find(conn_);
    if (it != ssh_->client_list.end()) {
        printf("start remove one connection.\n");
        libssh2_cpp::Ssh2Shell* tmp = it->second;
        // 释放连接对象
        it->second = nullptr;
        delete tmp;
        // 从map中删除
        ssh_->client_list.erase(it);
    }
    ssh_->list_mutex.unlock();
#else
    // 线程延迟方式来关闭连接
    std::thread( [conn_]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        conn_->terminate(websocketpp::lib::error_code());
    }).detach();
#endif
    printf("ssh_exit, remove one ssh succ.\n");
}

// 有输出
static void _ssh_out_msg(WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>* srv_, ssh_client_t* ssh_, ws_connection_hdl hdl, uint8_t* buf, uint32_t buf_len)
{
    auto conn_ = ((asio_server*)(srv_->get_endpoint()))->get_con_from_hdl(hdl);

    ssh_->list_mutex.lock();
    auto it = ssh_->client_list.find(conn_);
    if (it != ssh_->client_list.end()) {
        std::string strOut((const char*)buf, (const char*)&(buf[buf_len]));
        srv_->send_text_msg(hdl, strOut);
    }
    ssh_->list_mutex.unlock();
}

// Define a callback to handle incoming messages
static void _web_socket_open(WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>* srv_, ssh_client_t* ssh_, ws_connection_hdl& hdl, const Request<ws_request>& req)
{
    std::string strQuery;
    RequestQueryParams param_list;

    const std::string& strUri = req.get_uri();
    auto pos = strUri.find('?');
    if (pos != std::string::npos) {
        strQuery = strUri.substr(pos + 1);
    }
    if (!parse_query_list(strQuery, param_list)) {
        printf("parse_parameter_list fail.\n");
        return;
    }

#if 0
    for(auto map_item: param_list) {
        printf(" parameter key: %s, value: %s\n", map_item.first.c_str(), map_item.second.c_str());
    }
#else
    // ssh connection detail info
    const std::string& hostname = _get_param_in_list(param_list, "hostname", "127.0.0.1");
    const std::string& port     = _get_param_in_list(param_list, "port", "22");
	const std::string& user     = _get_param_in_list(param_list, "user", "");
	const std::string& passwd   = _get_param_in_list(param_list, "password", "");

    // pty info.
	const std::string& columns  = _get_param_in_list(param_list, "columns", "120");
	const std::string& rows     = _get_param_in_list(param_list, "rows", "80");

    libssh2_cpp::Ssh2Shell* ssh2_client_ = new libssh2_cpp::Ssh2Shell;
    //ssh2_->conn_ = conn_;

    libssh2_cpp::ErrorCode ret = ssh2_client_->init_libssh2();
    if (libssh2_cpp::noError != ret) {
        printf("init_libssh2 fail: %d.\n", ret);
        return;
    }
    ret = ssh2_client_->connect_ssh2(hostname.c_str(), atoi(port.c_str()));
    if (libssh2_cpp::noError != ret) {
        printf("connect_ssh2 fail: %d.\n", ret);
        return;
    }
    ret = ssh2_client_->user_auth1(user, passwd);
    if (libssh2_cpp::noError != ret) {
        printf("user_auth fail: %d.\n", ret);
        return;
    }

    auto conn_ = ((asio_server*)(srv_->get_endpoint()))->get_con_from_hdl(hdl);

    // 设置事件回调
    ssh2_client_->ChannelRead(std::bind(&_ssh_out_msg,    srv_, ssh_, conn_, std::placeholders::_1, std::placeholders::_2));
    ssh2_client_->ChannelEof(std::bind(&_ssh_exit,        srv_, ssh_, conn_));

    ret = ssh2_client_->open_shell(atoi(columns.c_str()), atoi(rows.c_str()));
    if (libssh2_cpp::noError != ret) {
        printf("open_shell fail: %d.\n", ret);
        return;
    }
    ret = ssh2_client_->run_shell();
    if (libssh2_cpp::noError != ret) {
        printf("run_shell fail: %d.\n", ret);
        return;
    }
    // 加入列表
    {
        ssh_->list_mutex.lock();
        ssh_->client_list.insert(std::pair<ws_connection_ptr, libssh2_cpp::Ssh2Shell* >(conn_, ssh2_client_));
        // 触发线程启动
        auto it = ssh_->client_list.find(conn_);
        if (it != ssh_->client_list.end()) {
            it->second->trigger_start();
        }
        ssh_->list_mutex.unlock();
    }
#endif
}

// Define a callback to handle incoming messages
static void _web_socket_close(WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>* srv_, ssh_client_t* ssh_, ws_connection_hdl& hdl, const Request<ws_request>& req)
{
    auto conn_ = ((asio_server*)(srv_->get_endpoint()))->get_con_from_hdl(hdl);

    ssh_->list_mutex.lock();
    auto it = ssh_->client_list.find(conn_);
    if (it != ssh_->client_list.end()) {
        printf("start remove one connection.\n");
        libssh2_cpp::Ssh2Shell* tmp = it->second;
        // 释放连接对象
        it->second = nullptr;
        delete tmp;
        // 从map中删除
        ssh_->client_list.erase(conn_);
    }
    ssh_->list_mutex.unlock();
    printf("remove one ssh succ.\n");
}

int main(int argc, char* argv[])
{
    ZipFileSystem zfs;
    // HTTP
    WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl> svr;

    // create a parser
    cmdline::parser cli_;

    ssh_client_t ssh_client_list;

    // 6th argument is extra constraint.
    // Here, port number must be 1 to 65535 by cmdline::range().
    cli_.add<uint32_t>("port",   'p', "listen port number", false, 8080, cmdline::range(1, 65535));
    cli_.add<std::string>("prefix", 'r', "url prefix", false, "");

    // Run parser.
    // It returns only if command line arguments are valid.
    // If arguments are invalid, a parser output error msgs then exit program.
    // If help flag ('--help' or '-?') is specified, a parser output usage message then exit program.
    cli_.parse_check(argc, argv);

    // 修改url_prefix
    std::string prefix = cli_.get<std::string>("prefix");
    if (prefix != "") {
        strPrefix = "url_prefix=";
        strPrefix += prefix;
        strPrefix += "&";
    }

    uint32_t assets_len = 0;
    const uint8_t* assets = NULL;
    get_web_assets_webssh(&assets, &assets_len);

    if (!zfs.init(assets, assets_len)) {
        printf("init zip file system fail.\n");
        return -1;
    }
    svr.set_fs_handle(&zfs);
    svr.set_fs_base_dir("/static");

    // 对于/与/index.html，都是返回index.html
    svr.Get("/$", websocketpp::lib::bind(_http_get_index_html, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    svr.Get("/index.html", websocketpp::lib::bind(_http_get_index_html, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    // 当WebSocket连接、断开时
    svr.OnOpen(websocketpp::lib::bind(_web_socket_open, &svr, &ssh_client_list, std::placeholders::_1, std::placeholders::_2));
    svr.OnClose(websocketpp::lib::bind(_web_socket_close, &svr, &ssh_client_list, std::placeholders::_1, std::placeholders::_2));
#if 0
    svr.OnError(websocketpp::lib::bind( [&](WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>* srv_, ws_connection_hdl& con, const Request<ws_request>& req) {
            printf("error in websocket.\n");
        }, &svr, std::placeholders::_1, std::placeholders::_2));
    svr.OnInterrupt(websocketpp::lib::bind( [&](WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>* srv_, ws_connection_hdl& con, const Request<ws_request>& req) {
            printf("interrupt in websocket.\n");
        }, &svr, std::placeholders::_1, std::placeholders::_2));
#endif
    svr.WsMsg("/ssh[\\w&\\?\\.#=]*", [&](ws_connection_hdl& hdl, const Request<ws_request>& req, const WebSocketMsg<ws_msg>& msg) {
        //std::cout << "payload: " << msg.get_payload() << std::endl;
        auto conn_ = ((asio_server*)(svr.get_endpoint()))->get_con_from_hdl(hdl);

        //printf("conn_: %p.\n", conn_.get());
        auto it = ssh_client_list.client_list.find(conn_);
        if (it != ssh_client_list.client_list.end()) {
            //std::cout << "channel_write_ex: " << msg.get_payload() << std::endl;
            it->second->channel_write_ex(0, msg.get_payload().c_str(), msg.get_payload().size());
        }
    });

    if (svr.init()) {
        svr.run(cli_.get<uint32_t>("port"));
    }
    // 释放
    {
        ssh_client_list.list_mutex.lock();
        for(auto item: ssh_client_list.client_list) {
            printf("start remove one connection.\n");
            libssh2_cpp::Ssh2Shell* tmp = item.second;
            // 释放连接对象
            item.second = nullptr;
            delete tmp;
            // 从map中删除
            ssh_client_list.client_list.erase(item.first);
        }
        ssh_client_list.list_mutex.unlock();
    }
}